//Include libraries
#include <avr/pgmspace.h>
#include <MeggyJr.h>
#include <stdlib.h>
#include <ctype.h>
#include "Mario.h"

//Engine MACROS
#define X_SIZE              8
#define Y_SIZE              8
#define START_DISPLACEMENT  0
#define DISPLACEMENT_SIZE   3

//CLASSIC MODE
  /*Classical Mode is 1, No moving to the left
    Normal Mode is 2, No restrictions, free moving
   */
#define MODE 1

//Game MACROS
#define ENEMY_DELAY        45
#define PLAYER_DELAY       10
#define TIME_DELAY          5
#define PLAYER_JUMP_HEIGHT  3
#define PLAYER_START_STATE  1
#define PLAYER_START_COLOR  {15, 0, 0}

//Level MACROS
#define LEVEL_LENGTH    168
#define LEVEL_WIDTH       8
#define NUM_OF_ENEMIES    19
#define CT_SIZE           10
#define COLUMN_7      "b !    bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb                  bbbbbbbbbbbbbbbbbbbbbbbbbb     b"
#define COLUMN_6      "b                                            bb  bbbb  bbbb      bbbb                                                                        ggbb                     b"
#define COLUMN_5      "b                                          bb       b  bbbb   0  bbbb                                                                        ggbb                     b"
#define COLUMN_4      "b                               b bbbb b   bb       b   b    bb          bbbb              0                       0              oooo     ggggbb  WWWW   WWW    WW   b"
#define COLUMN_3      "b                 b b   o       bbb  bbb   bbbb  bbbb   b    bb  00      bbbb       0     ggg     0     bb        0bb      bbbbb           ggggbb                     b"
#define COLUMN_2      "b      oooo   0 b b b b   b                  bb         bbb  bb  bbbb              ggg    ggg    ggg    bb        bbb oooo             bbbbbbbbbb   ggg   ggg   ggg   b"
#define COLUMN_1      "b            0b b b b b 0 b b        00           0 0 0                         00 ggg    ggg  0 ggg    bb       bbbb       0          bbbbbbbbbb   ggg   ggg   ggg   b"
#define COLUMN_0      "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb   bbbbbbbbbbbgggbbbbgggbbbbgggbb  bb  bbbbbbbbb      bbbbbb      bbbbbbbbbbbbbgggbbbgggbbbgggbbbb"

//Color MACROS
#define ALL_OFF       { 0,  0,  0}
#define ALL_ON        {15, 15, 15}
#define DEFAULT_COLOR 0

//Color tables for level and enemies
color colorTable[CT_SIZE] = {{' ', { 0,  0,  0}},
                             {'b', { 0,  0,  2}},
                             {'g', { 1, 15,  0}},
                             {'o', {12, 15,  0}},
                             {'r', { 6,  3,  0}},
                             {'B', { 0,  0,  0}},
                             {'W', { 3, 15,  2}},
                             {'P', { 8,  0,  4}},
                             {'G', { 1, 15,  0}},
                             {'R', { 6,  3,  0}}};                             
color enemiesColor[NUM_OF_ENEMIES] = {'0', {15, 15, 15}};

//Initialize variables
MeggyJr Meg;
character protagonist, *enemy;
scene **frame, characterFrame[8][8];
uint8_t pJump = 0, pPosition[2], pDelay = 0, displacement, enemyDirection[NUM_OF_ENEMIES], enemyDelay[NUM_OF_ENEMIES];
prog_char level[LEVEL_WIDTH][LEVEL_LENGTH] PROGMEM = {COLUMN_0, COLUMN_1, COLUMN_2, COLUMN_3, COLUMN_4, COLUMN_5, COLUMN_6, COLUMN_7};
byte Button_A, Button_B, Button_Up, Button_Down, Button_Left, Button_Right;

int isCapital (char character){
  if ((int) character >= 65 && (int) character <= 90){
    return 1;
  }
  else{
    return 0;
  }
}

int isLowcase (char character){
  if ((int) character >= 97 && (int) character <= 122){
    return 1;
  }
  else{
    return 0;
  }
}

int isNumeric (char character){
  if ((int) character >= 48 && (int) character <= 57){
    return 1;
  }
  else{
    return 0;
  }
}

void windowFix (uint8_t whereTo[], int *value){
  for (int i = 0;i < 3;i++){
    whereTo[i] = value[i];
  }
}

void clearDisplay(){
  for (int i = 0;i < 8;i++){
    for (int j = 0;j < 8;j++){
      Meg.ClearPixel(i, j);
    }
  }
}

void CheckButtonsDown(){ 
  byte i = Meg.GetButtons();
  Button_B  = (i & 1);      
  Button_A = (i & 2);     
  Button_Up = (i & 4);
  Button_Down = (i & 8);
  Button_Left = (i & 16);
  Button_Right = (i & 32);
}

void makeFrame (int start){
  frame = (scene **)calloc(X_SIZE, sizeof(scene *));
  char currentChar;
  for (int i = 0;i < X_SIZE;i++){
    *(frame + i) = (scene *)calloc(Y_SIZE, sizeof(scene));
    for (int j = 0;j < Y_SIZE;j++){
      currentChar = pgm_read_byte(&level[j][i + start]);
      if (currentChar != ' '){
        if (isLowcase(currentChar)){
          (*(frame + i) + j)->set = 1;
        }else if(isCapital(currentChar)){
          (*(frame + i) + j)->set = 2;
        }else{
          (*(frame + i) + j)->set = 0;
        }
        for (int k = 0;k < 3;k++){
          (*(frame + i) + j)->color[k] = colorTable[DEFAULT_COLOR].color[k];
        }      
        for (int k = 0;k < CT_SIZE;k++){
          if (colorTable[k].symbol == currentChar){
            for (int l = 0;l < 3;l++){
              (*(frame + i) + j)->color[l] = colorTable[k].color[l];
            }
            k = CT_SIZE;
          }
        }
      }else{
        (*(frame + i) + j)->set = 0;
        for (int k = 0;k < 3;k++){
          (*(frame + i) + j)->color[k] = colorTable[DEFAULT_COLOR ].color[k];
        }
      }
    }     
  }
}

void getPlayerPosition(){
  uint8_t found = 0;
  for(int i = 0;i < LEVEL_LENGTH && !found;i++){
    for(int j = 0;j < 8 && !found;j++){
      if (pgm_read_byte(&level[j][i]) == '!'){
        found++;
        pPosition[0] = i;
        pPosition[1] = j;
      }
    }
  }
}

void getEnemyLocations(){
  char currentLocation;
  int enemiesFound = 0;
  for (int i = 0;i < LEVEL_LENGTH && enemiesFound < NUM_OF_ENEMIES;i++){
    for (int j = 0;j < 8 && enemiesFound < NUM_OF_ENEMIES;j++){
      currentLocation = pgm_read_byte(&level[j][i]);
      if (isNumeric(currentLocation)){
        (enemy + enemiesFound)->x = i;        
        (enemy + enemiesFound)->y = j;
        (enemy + enemiesFound)->state = 2;
        enemyDirection[enemiesFound] = 0;
        enemyDelay[enemiesFound] = ENEMY_DELAY;
        for (int k = 0, found =0;k < NUM_OF_ENEMIES && found == 0;k++){
          if(enemiesColor[k].symbol == currentLocation){
            found++; 
            for (int l = 0;l < 3;l++){              
              (enemy + enemiesFound)->color[l] = enemiesColor[k].color[l];
            }
          }
        }        
        enemiesFound++;
      }
    }
  }
}

void moveEnemies(){
  for (int i = 0;i < NUM_OF_ENEMIES;i++){
    if ((enemy + i)->state == 1){
      if (enemyDelay[i] == 0){
        if((enemyDirection[i] == 1 && !isPassable((enemy + i)->x + 1, (enemy + i)->y)) || (enemy + i)->x == LEVEL_LENGTH){        
          enemyDirection[i] = 0;
        }else if ((enemyDirection[i] == 0 && !isPassable((enemy + i)->x - 1, (enemy + i)->y)) || (enemy + i)->x == 0){
          enemyDirection[i] = 1;
        }

        if (!isPassable((enemy + i)->x, (enemy + i)->y - 1)){
          if ((enemy + i)->x - 1 >= 0 && enemyDirection[i] == 0 && isPassable((enemy + i)->x - 1, (enemy + i)->y)){
            (enemy + i)->x--;
          }else if ((enemy + i)->x + 1 <= LEVEL_LENGTH && enemyDirection[i] == 1 && isPassable((enemy + i)->x + 1, (enemy + i)->y)){
            (enemy + i)->x++;
          }
        }
        if (isPassable((enemy + i)->x, (enemy + i)->y - 1) && (enemy + i)->y >= 1){
          (enemy + i)->y--;
        }else if((enemy + i)->y == 0){
          (enemy + i)->state--;
        }
        enemyDelay[i] = ENEMY_DELAY;
      }else{
        enemyDelay[i]--;
      }
      if ((enemy + i)->x - displacement == protagonist.x && (enemy + i)->y + 1 == protagonist.y && (enemy + i)->state > 0){
        (enemy + i)->state = 0;
        pJump = PLAYER_JUMP_HEIGHT;
      }else if (((enemy + i)->x - displacement == protagonist.x && (enemy + i)->y == protagonist.y && protagonist.state > 0)){
        protagonist.state--;
      } 
    }else if((enemy + i)->state == 2){
      if ((enemy + i)->x - displacement >= 0 && (enemy + i)->x - displacement <= 7){
        (enemy + i)->state--;
      }
    }
  }
}

void placeEnemies(){
  for(int i = 0;i < NUM_OF_ENEMIES;i++){
    if ((enemy + i)->x - displacement >= 0 && (enemy + i)->x - displacement <= 7 && (enemy + i)->y >= 0 && (enemy + i)->y <= 7 && characterFrame[(enemy + i)->x - displacement][(enemy + i)->y].set == 0 && (enemy + i)->state == 1){
      characterFrame[(enemy + i)->x - displacement][(enemy + i)->y].set = 1;
      for (int k = 0;k < 3;k++){
        characterFrame[(enemy + i)->x - displacement][(enemy + i)->y].color[k] = (enemy + i)->color[k];
      }
    }
  }
}

int isPassable(uint8_t x, uint8_t y){
  char value = pgm_read_byte(&level[y][x]);
  if (isCapital(value) || isNumeric(value) || value == ' '){
    return 1;
  }else if(isLowcase(value)){
    return 0;
  }else{
    return 0;
  }
}

void placeCharacter (character player){
  if (player.state >= 1){
    if(player.x < 8 && player.y < 8){
      characterFrame[player.x][player.y].set = 1;
      for (int i = 0;i < 3;i++){
        characterFrame[player.x][player.y].color[i] = player.color[i];
      }
      Meg.AuxLEDs = 0;
    }else if(player.y == 8){
      Meg.AuxLEDs = 1 << player.x;
    }else{
      Meg.AuxLEDs = 0;
    }
  }
}

void clearCharacters(){
  for (int i = 0;i < 8;i++){
    for (int j = 0;j < 8;j++){
      characterFrame[i][j].set = 0;
      for (int k = 0;k < 3;k++){
        characterFrame[i][j].color[k] = 0;
      }
    }
  }
}

void printFrame(){
  uint8_t blackColor[3] = {0, 0, 0};
  for (int i = 0;i < 8;i++){
    for (int j = 0;j < 8;j++){      
      if (characterFrame[i][j].set >= 1){
        Meg.SetPxClr(i, j, characterFrame[i][j].color);
      }else{
        Meg.SetPxClr(i, j, (*(frame + i) + j)->color);
      }
    }
  }
}

void setup(){
  //Serial.begin(19200);
  char rgb[3] = PLAYER_START_COLOR;
  getPlayerPosition(); 
  protagonist.x = pPosition[0];  
  protagonist.y = pPosition[1];  
  protagonist.state = PLAYER_START_STATE;
  for (int i = 0;i < 3;i++){
    protagonist.color[i] = rgb[i];
  }
  
  enemy = (character *)calloc(NUM_OF_ENEMIES, sizeof(character));
  Meg  = MeggyJr();
  pDelay = PLAYER_DELAY;
  displacement = START_DISPLACEMENT;
  getEnemyLocations();
}

void loop(){
  delay(TIME_DELAY + 1);

  CheckButtonsDown();
  clearCharacters();
  makeFrame(displacement);

  if (pDelay == 0){
    if (protagonist.state >= 1){
      if (pJump > 0){
        if (isPassable(protagonist.x + displacement, protagonist.y + 1) || protagonist.y >= 7){
          pJump--;
          protagonist.y++;  
        }else{
          pJump = 0;
        }
      }else if (isPassable(protagonist.x + displacement, protagonist.y - 1) || protagonist.y == 0 || protagonist.y > 8){
        if(protagonist.y == 0){
          protagonist.state = 0;
        }
        else{
          protagonist.y--;
        }      
      }

      if (Button_Left && (isPassable(protagonist.x + displacement - 1, protagonist.y) || protagonist.y >= 8) && protagonist.x > 0){
#      if MODE == 1
        protagonist.x--;
#      elif MODE == 2
        if (displacement > 0 && protagonist.x == 0 + DISPLACEMENT_SIZE){
          displacement--;
        }else if (protagonist.x != 0){
          protagonist.x--;
        }
#      endif
      }

      if (Button_Right && (isPassable(protagonist.x + displacement + 1, protagonist.y) ||  protagonist.y >= 8)){
        if (displacement < LEVEL_LENGTH - 9 && protagonist.x == 7 - DISPLACEMENT_SIZE){
          displacement++;
        }else if (protagonist.x != 7){
          protagonist.x++;
        }
      }

      if (Button_A && pJump == 0 && !isPassable(protagonist.x + displacement, protagonist.y - 1) && protagonist.y <= 8 && protagonist.y != 0){
        pJump = PLAYER_JUMP_HEIGHT;
      }     
          
      pDelay = PLAYER_DELAY;
    }
  }else{
    pDelay--;
  } 

  if (Button_B || protagonist.state == 0){
    displacement = START_DISPLACEMENT;
    pJump = 0;
    protagonist.state = 1;
    protagonist.x = pPosition[0];
    protagonist.y = pPosition[1];
  }

  placeCharacter(protagonist);
  moveEnemies();
  placeEnemies();
  printFrame();

  for(int i = 0;i < 8;i++){
    free(*(frame + i));
  }
  free(frame);
}


